package com.hfzy.ihk.common.support.redis;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.hfzy.ihk.common.util.string.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cache.CacheManager;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import redis.clients.jedis.JedisPoolConfig;

import java.lang.reflect.Method;
import java.time.Duration;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * Created by Administrator on 2017/12/19.
 * 该配置作用：
 * 避免spring-data-elasticsearch与spring-data-redis自动配置的冲突
 * 如果使用mybatis，可开启配置redis作为mybatis的二级缓存（不建议用）
 * @author wws
 */
@ConditionalOnProperty(prefix = "spring.redis.customConfig",name = "enable" )
/*使用CGLib动态代理*/
//@EnableAspectJAutoProxy(proxyTargetClass = true)
/*不使用spring aop默认的jdk动态代理方式，可以避免方法内部调用有缓存的方法不起效,
    但是基于类的动态代理（CGLIB），@Cache*注解就无法使用在接口上*/
//@EnableCaching(mode = AdviceMode.ASPECTJ,proxyTargetClass = true)
//@EnableLoadTimeWeaving(aspectjWeaving = EnableLoadTimeWeaving.AspectJWeaving.ENABLED)
//@EnableCaching
public class RedisConfig {

    Logger log = LoggerFactory.getLogger(RedisConfig.class);

    @Value("${spring.redis.cache.database}")
    Integer cacheDatabase;

    @Value("${spring.redis.database}")
    Integer database;
    @Value("${spring.redis.host}")
    String host;
    @Value("${spring.redis.port}")
    Integer port;
    @Value("${spring.redis.password}")
    String psw;
    @Value("${spring.redis.pool.max-active}")
    Integer maxActive;
    @Value("${spring.redis.pool.max-idle}")
    Integer maxIdle;
    @Value("${spring.redis.pool.min-idle}")
    Integer minIdle;
    @Value("${spring.redis.sentinel.master}")
    String masterName;
    @Value("${spring.redis.sentinel.nodes}")
    String sentinelNodes;
    @Value("${spring.redis.cluster.nodes}")
    String clusterNodes;

    @Bean
    public JedisPoolConfig jedisPoolConfig(){
        /*
        <!--最大连接数-->
		<property name="maxTotal" value="500" />
		<!--最大空闲连接数,默认8-->
		<property name="maxIdle" value="50" />
		<!--默认0-->
		<property name="minIdle" value="20" />
		<!--连接耗尽时是否阻塞, false报异常,ture阻塞直到超时, 默认true-->
		<property name="blockWhenExhausted" value="true" />
		<!--获取连接时的最大等待毫秒数(如果设置为阻塞时BlockWhenExhausted),如果超时就抛异常, 小于零:阻塞不确定的时间,  默认-1-->
		<property name="maxWaitMillis" value="-1" />
		<!--逐出连接的最小空闲时间，jedis默认1800000毫秒(30分钟)，服务器默认300秒，客户端应当比服务器的还小-->
		<property name="minEvictableIdleTimeMillis" value="1800000" />
		<!--逐出扫描的时间间隔(毫秒)，如果为负数,则不运行逐出线程-->
		<property name="timeBetweenEvictionRunsMillis" value="-1" />
		<!--每次逐出检查时 逐出的最大数目,如果为负数就是 : 1/abs(n), 默认3 -->
		<property name="numTestsPerEvictionRun" value="3" />
		<!--对象空闲多久后逐出, 当空闲时间>该值 且 空闲连接>最大空闲数 时直接逐出,不再根据MinEvictableIdleTimeMillis判断  (默认逐出策略)   -->
		<property name="softMinEvictableIdleTimeMillis" value="1800000"/>
		<!--在获取连接的时候检查有效性, 默认false-->
		<property name="testOnBorrow" value="false" />
		<property name="testOnReturn" value="false" />
		<!--在空闲时检查有效性, 默认false-->
		<property name="testWhileIdle" value="true" />
        */
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxTotal(maxActive);
        jedisPoolConfig.setMaxIdle(maxIdle);
        jedisPoolConfig.setMinIdle(minIdle);
        jedisPoolConfig.setTestWhileIdle(true);

        return jedisPoolConfig;
    }

    @Bean("redisSentinelConfiguration")
    @ConditionalOnProperty(prefix = "spring.redis.sentinel",name = "nodes",matchIfMissing = false)
    public RedisSentinelConfiguration redisSentinelConfiguration(){
        return createRedisSentinelConfiguration(database);
    }
    @Bean("redisCacheSentinelConfiguration")
    @ConditionalOnProperty(prefix = "spring.redis.sentinel",name = "nodes",matchIfMissing = false)
    public RedisSentinelConfiguration redisCacheSentinelConfiguration(){
        return createRedisSentinelConfiguration(cacheDatabase);
    }

    public RedisSentinelConfiguration createRedisSentinelConfiguration(int database){
        if(StringUtil.isEmpty(sentinelNodes)) {
            log.debug("不初始化 redisSentinel",database);
            return null;
        }
        log.debug("初始化 redisSentinelConfiguration,databaseId:{}",database);

        String[] sentinelNodeArray = sentinelNodes.split(",");
        log.debug("---sentinelNodes---:{},masterName:{}",sentinelNodes,masterName);
        Set<String> sentinelHostAndPorts =  new HashSet();
        for(String node:sentinelNodeArray){
            sentinelHostAndPorts.add(node);
        }
        RedisSentinelConfiguration redisSentinelConfiguration = new RedisSentinelConfiguration(masterName,sentinelHostAndPorts);
        redisSentinelConfiguration.setDatabase(database);
        redisSentinelConfiguration.setPassword(RedisPassword.of(psw));
        return redisSentinelConfiguration;
    }

    @Bean
    @ConditionalOnProperty(prefix = "spring.redis.cluster",name = "nodes",matchIfMissing = false)
    public RedisClusterConfiguration redisClusterConfiguration(){
        return createRedisClusterConfiguration();
    }

    public RedisClusterConfiguration createRedisClusterConfiguration(){
        if(StringUtil.isEmpty(clusterNodes)) {
            log.debug("不初始化 redis cluster",database);
            return null;
        }

        log.debug("初始化 redisClusterConfiguration");
        String[] clusterNodeArray = clusterNodes.split(",");
        log.debug("---clusterNodes---:{}",clusterNodes);
        List list = new ArrayList<>();
        for(String node:clusterNodeArray){
            list.add(node);
        }
        RedisClusterConfiguration redisClusterConfiguration = new RedisClusterConfiguration(list);
        redisClusterConfiguration.setPassword(RedisPassword.of(psw));
        return redisClusterConfiguration;
    }

    private JedisConnectionFactory createRedisConnectionFactory(RedisSentinelConfiguration sentinelConfiguration,
                                                                RedisClusterConfiguration clusterConfiguration
    ){

        validateConfig();

        JedisConnectionFactory jedisConnectionFactory;
        if(sentinelConfiguration!=null) {
            log.debug("为redis哨兵初始化连接池");
            jedisConnectionFactory = new JedisConnectionFactory(sentinelConfiguration,jedisPoolConfig());
        }else if (clusterConfiguration!=null){
            log.debug("为redis集群初始化连接池");
            jedisConnectionFactory = new JedisConnectionFactory(clusterConfiguration,jedisPoolConfig());
        }else {
            log.debug("为redis单机初始化连接池");
            jedisConnectionFactory = new JedisConnectionFactory(jedisPoolConfig());
            jedisConnectionFactory.setHostName(host);
            jedisConnectionFactory.setPort(port);
            jedisConnectionFactory.setPassword(psw);
        }
        return jedisConnectionFactory;
    }

    /**===================================常规redis存储 database:0================================================*/
    @Bean("redisConnectionFactory")
    public JedisConnectionFactory redisConnectionFactory(){

        log.debug("初始化 主 redisConnectionFactory,databaseId:{}",database);

        return createRedisConnectionFactory(redisSentinelConfiguration(),redisClusterConfiguration());
    }

    /**
     * 重写Redis序列化方式，使用Json方式:
     * 当我们的数据存储到Redis的时候，我们的键（key）和值（value）都是通过Spring提供的Serializer序列化到数据库的。
     * RedisTemplate默认使用的是JdkSerializationRedisSerializer，StringRedisTemplate默认使用的是StringRedisSerializer。
     * Spring Data JPA为我们提供了下面的Serializer：
     * GenericToStringSerializer、Jackson2JsonRedisSerializer、JacksonJsonRedisSerializer、
     * JdkSerializationRedisSerializer、OxmSerializer、StringRedisSerializer。
     * 在此我们将自己配置RedisTemplate并定义Serializer。
     * @param redisConnectionFactory
     * @return
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(@Qualifier("redisConnectionFactory") JedisConnectionFactory redisConnectionFactory) {

        log.debug("--redisTemplate----redis序列化方式重写为json-----databaseId:"+redisConnectionFactory.getDatabase());

        return createRedisTemplate(redisConnectionFactory);
    }

    private RedisTemplate<String, Object> createRedisTemplate(RedisConnectionFactory redisConnectionFactory){

        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);

        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);

        // 设置值（value）的序列化采用Jackson2JsonRedisSerializer。
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        // 设置键（key）的序列化采用StringRedisSerializer。
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);

        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

    @Bean
    public StringRedisTemplate stringRedisTemplate(@Qualifier("redisConnectionFactory") JedisConnectionFactory redisConnectionFactory) {

        log.debug("--stringRedisTemplate---初始化-----databaseId:"+redisConnectionFactory.getDatabase());

        StringRedisTemplate stringRedisTemplate = new StringRedisTemplate();
        stringRedisTemplate.setConnectionFactory(redisConnectionFactory);
        stringRedisTemplate.afterPropertiesSet();
        return stringRedisTemplate;
    }

    private void validateConfig(){

        if(database.equals(cacheDatabase)){
            throw new RuntimeException("redis缓存库不能与redis常用库相同");
        }
    }
    /**=======================================发布订阅==============================================*/

     /*
     * Redis消息监听器容器
     * 这个容器加载了RedisConnectionFactory和消息监听器
     */
   /* @Bean
    RedisMessageListenerContainer container(@Qualifier("redisConnectionFactory") RedisConnectionFactory redisConnectionFactory,
                                            CodeDetailMessageSyncListener codeDetailMessageSyncListener,
                                            ZyUserMessageSyncListener zyUserMessageSyncListener){
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(redisConnectionFactory);
        container.addMessageListener(codeDetailMessageSyncListener, new PatternTopic(CodeDetailMessageSyncListener.CODE_DETAIL_PATTERN));
        container.addMessageListener(zyUserMessageSyncListener, new PatternTopic(ZyUserMessageSyncListener.ZY_USER_PATTERN));
        return container;
    }*/

    /**=======================================springCache===================================================*/
    @Bean("springCacheRedisConnectionFactory")
    public JedisConnectionFactory springCacheRedisConnectionFactory(){

        log.debug("初始化 springCacheRedisConnectionFactory");

        return createRedisConnectionFactory(redisCacheSentinelConfiguration(),redisClusterConfiguration());

    }

    @Bean
    public RedisTemplate<String, Object> springCacheRedisTemplate(@Qualifier("springCacheRedisConnectionFactory") JedisConnectionFactory springCacheRedisConnectionFactory) {

        log.debug("初始化---springCacheRedisTemplate---redis序列化方式重写为json-----databaseId:"+springCacheRedisConnectionFactory.getDatabase());

        return createRedisTemplate(springCacheRedisConnectionFactory);
    }
    /**
     * 设置RedisCacheManager
     * 使用cache注解管理redis缓存
     *
     * @return
     */
    @Bean
    public CacheManager cacheManager(@Qualifier("springCacheRedisConnectionFactory") JedisConnectionFactory springCacheRedisConnectionFactory) {

        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);

        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofSeconds(3600))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer));
//                .disableCachingNullValues();
        RedisCacheManager redisCacheManager = RedisCacheManager.builder(springCacheRedisConnectionFactory)
                .cacheDefaults(config)
                .transactionAware()
                .build();
        return redisCacheManager;
    }

    /*@Bean
    @Qualifier("springCacheRedisTemplate")
    public CacheManager cacheManager(RedisTemplate springCacheRedisTemplate) {
        RedisCacheManager redisCacheManager = new RedisCacheManager(springCacheRedisTemplate);
        redisCacheManager.setDefaultExpiration(3600);
        return redisCacheManager;
    }*/

    /**
     *
     * 这里用于复杂的查询的key生产：
     *  就是类名+方法名+参数的key
     *
     * @return
     */
    @Bean
    public KeyGenerator keyGenerator() {
        return new KeyGenerator() {
            @Override
            public Object generate(Object serviceObj, Method method, Object... params) {
                StringBuilder sb = new StringBuilder();
                sb.append(serviceObj.getClass().getName()).append(".");
                sb.append(method.getName()).append(".");
                for (Object obj : params) {
                    sb.append(obj.toString());
                }
                System.out.println("keyGenerator=" + sb.toString());
                return sb.toString();
            }
        };
    }

//    @Bean
//    public KeyGenerator keyGenerator() {
//        return new KeyGenerator() {
//            @Override
//            public Object generate(Object target, Method method, Object... params) {
//                StringBuilder sb = new StringBuilder();
//                String[] cacheName = new String[1];
//                Cacheable cacheable = method.getAnnotation(Cacheable.class);
//                if (cacheable != null) {
//                    cacheName = cacheable.cacheNames();
//                }
//                CachePut cachePut = method.getAnnotation(CachePut.class);
//                if (cachePut != null) {
//                    cacheName = cachePut.cacheNames();
//                }
//                CacheEvict cacheEvict = method.getAnnotation(CacheEvict.class);
//                if (cacheEvict != null) {
//                    cacheName = cacheEvict.cacheNames();
//                }
//                sb.append(target.getClass().getName());
//                sb.append(".");
//                sb.append(cacheName[0]);
//                sb.append(".");
//                for (Object obj : params) {
//                    sb.append(":" + obj.toString());
//                }
//                System.out.println("chacheKey:"+sb.toString());
//                return sb.toString();
//            }
//        };
//    }

}
